package cn.monkey.transport.netty;

import cn.monkey.transport.netty.utils.MapUtils;
import io.netty.bootstrap.AbstractBootstrap;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.AttributeKey;
import io.netty.util.internal.ObjectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLEngine;
import java.net.SocketAddress;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * @param <BOOTSTRAP>
 * @param <T>
 * @param <B>
 * @param <C>
 */

public abstract class TransportConfig<BOOTSTRAP extends AbstractBootstrap<BOOTSTRAP, C>, T extends TransportConfig<BOOTSTRAP, T, B, C>, B extends TransportConfig<BOOTSTRAP, T, B, C>.Builder, C extends Channel> {
    private static final Logger log = LoggerFactory.getLogger(TransportConfig.class);
    protected Consumer<Channel> channelInitCustomizer;
    protected Map<ChannelOption<?>, ?> channelOptions;
    protected Map<AttributeKey<?>, ?> channelAttributes;
    protected Function<BOOTSTRAP, BOOTSTRAP> bootstrapCustomizer;
    protected EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    protected ChannelFactory<? extends C> channelFactory;
    protected List<Supplier<SocketAddress>> socketAddresses;
    protected ShutDown shutDown;

    public Function<BOOTSTRAP, BOOTSTRAP> bootstrapCustomizer() {
        return this.bootstrapCustomizer;
    }

    public List<Supplier<SocketAddress>> socketAddresses() {
        return this.socketAddresses;
    }

    public Consumer<Channel> channelInitCustomizer() {
        return channelInitCustomizer;
    }

    public Map<ChannelOption<?>, ?> channelOptions() {
        return channelOptions;
    }

    public Map<AttributeKey<?>, ?> channelAttributes() {
        return channelAttributes;
    }

    public EventLoopGroup eventLoopGroup() {
        return eventLoopGroup;
    }

    public ChannelFactory<? extends C> channelFactory() {
        return this.channelFactory;
    }

    public ShutDown shutDown() {
        return shutDown;
    }

    public TransportConfig() {
    }

    public abstract B mutate();


    public class Builder {

        protected final T instance;

        protected Builder(T instance) {
            this.instance = instance;
        }

        @SuppressWarnings("unchecked")
        public B duplicate() {
            return (B) this;
        }

        public <V> B attr(AttributeKey<V> key, V val) {
            Objects.requireNonNull(key, "key");
            return attributes(MapUtils.updateMap(instance.channelAttributes, key, val));
        }

        public B socketAddress(Supplier<SocketAddress> socketAddressSupplier) {
            List<Supplier<SocketAddress>> socketAddresses = this.instance.socketAddresses;
            if (socketAddresses == null) {
                socketAddresses = new ArrayList<>();
            } else {
                socketAddresses = new ArrayList<>(socketAddresses);
            }
            socketAddresses.add(socketAddressSupplier);
            this.instance.socketAddresses = Collections.unmodifiableList(socketAddresses);
            return duplicate();
        }

        public <O> B channelOption(ChannelOption<O> key, O val) {
            Objects.requireNonNull(key, "key");
            // Reference comparison is deliberate
            if (ChannelOption.AUTO_READ == key) {
                if (val instanceof Boolean && Boolean.TRUE.equals(val)) {
                    log.error("ChannelOption.AUTO_READ is configured to be [false], it cannot be set to [true]");
                }
                return duplicate();
            }
            Map<ChannelOption<?>, ?> channelOptionMap = MapUtils.updateMap(instance.channelOptions, key, val);
            return channelOptions(channelOptionMap);
        }

        public B doOnChannelInit(Consumer<Channel> channelInitCustomizer) {
            if (this.instance.channelInitCustomizer == null) {
                this.instance.channelInitCustomizer = channelInitCustomizer;
                return duplicate();
            }
            this.instance.channelInitCustomizer = this.instance.channelInitCustomizer.andThen(channelInitCustomizer);
            return duplicate();
        }

        public B channelClass(Class<? extends C> channelClass) {
            return this.channelFactory(new ReflectiveChannelFactory<>(ObjectUtil.checkNotNull(channelClass, "channelClass")));
        }

        public B channelFactory(ChannelFactory<? extends C> channelFactory) {
            ChannelFactory<? extends C> factory = this.instance.channelFactory;
            if (factory != null) {
                throw new UnsupportedOperationException("channel factory has bean already configured!");
            }
            this.instance.channelFactory = channelFactory;
            return duplicate();
        }

        public B channelOptions(Map<ChannelOption<?>, ?> options) {
            this.instance.channelOptions = options;
            return duplicate();
        }


        public B attributes(Map<AttributeKey<?>, ?> attributes) {
            this.instance.channelAttributes = attributes;
            return duplicate();
        }

        public B eventLoopGroup(EventLoopGroup eventLoopGroup) {
            this.instance.eventLoopGroup = eventLoopGroup;
            return duplicate();
        }

        public B shutDown(ShutDown shutDown) {
            this.instance.shutDown = shutDown;
            return duplicate();
        }

        public B bootstrapCustomizer(Function<BOOTSTRAP, BOOTSTRAP> customizer) {
            Function<BOOTSTRAP, BOOTSTRAP> bootstrapCustomizer = this.instance.bootstrapCustomizer;
            if (bootstrapCustomizer == null) {
                this.instance.bootstrapCustomizer = customizer;
            } else {
                this.instance.bootstrapCustomizer = bootstrapCustomizer.andThen(customizer);
            }
            return duplicate();
        }

        public T build() {
            return instance;
        }
    }
}
